package com.mokylin.dbschema.handler;

import com.mokylin.dbschema.config.ConfigInfo;
import com.mokylin.dbschema.util.FileUtils;
import com.mokylin.dbschema.util.GlobalUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.*;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * sql文件版本处理器，默认sql文件命名规则为：name_version.sql ；如：recharge_1.sql
 *
 * @author 李朝(Li.Zhao)
 * @since 2015/8/31.
 */

public class DefaultSqlVersionHandlerImpl implements SqlVersionHandler {
    private static final Logger LOG = LoggerFactory.getLogger(DefaultSqlVersionHandlerImpl.class);

    private String versionSeparator = "_";

    /**
     * sql文件版本获取方法，默认sql文件命名规则为：name_version.sql ；如：recharge_1.sql
     */
    private Function<String, String> sqlFileVersionConverter = fileName -> fileName.substring(fileName.lastIndexOf(versionSeparator) + 1, fileName.lastIndexOf("."));

    /**
     * sql文件版本排序,从小到大
     */
    private Comparator<File> sqlFileComparator = (a, b) -> Integer.valueOf(sqlFileVersionConverter.apply(a.getName())).compareTo(Integer.valueOf(sqlFileVersionConverter.apply(b.getName())));

    private Comparator<String> sqlVersionComparator = (a, b) -> Integer.valueOf(a).compareTo(Integer.valueOf(b));


    private List<File> sortedAllFiles;

    public void setSqlFileVersionConverter(Function<String, String> sqlFileVersionConverter) {
        this.sqlFileVersionConverter = sqlFileVersionConverter;
    }

    public void setSqlFileComparator(Comparator<File> sqlFileComparator) {
        this.sqlFileComparator = sqlFileComparator;
    }

    @Override
    public String getCurrentVersion() {
        List<File> fileNames = getSortSqlFiles();
        if (fileNames.isEmpty()) {
            return "";
        }
        return sqlFileVersionConverter.apply(fileNames.get(fileNames.size() - 1).getName());
    }

    private List<File> getSortSqlFiles() {
        if(sortedAllFiles!=null){
            return sortedAllFiles;
        }
        Enumeration<URL> resources = null;
        try {
            resources = DefaultSqlVersionHandlerImpl.class.getClassLoader().getResources(ConfigInfo.getSqlDir());
        } catch (IOException e) {
            LOG.error(e.getMessage(),e);
            return Collections.EMPTY_LIST;
        }
        URL resource = null;
        if (resources.hasMoreElements()) {
            resource = resources.nextElement();
        }
        if(resources.hasMoreElements()){
            LOG.warn("no unique sqls dir,{},{}",resource,resources.nextElement());
        }
        File sqlDir;
        List<File> sqlFileList = null;
        if (resource == null) {
            sqlDir = new File(ConfigInfo.getSqlDir());
            if (!sqlDir.exists()) {
                return Collections.EMPTY_LIST;
            } else {
                sqlFileList = Arrays.asList(sqlDir.listFiles((dir, name) -> name.endsWith(".sql") && name.matches("\\D*\\d\\.sql")));
            }
        } else {
            LOG.info("sql dir resource:{}", resource.getFile());
            String dirFile = null;
            try {
                dirFile = URLDecoder.decode(resource.getFile(), Charset.defaultCharset().name());
            } catch (UnsupportedEncodingException e) {
                LOG.error(e.getMessage());//ignore
                dirFile = resource.getFile();
            }
            if ("file".equals(resource.getProtocol())) {
                sqlDir = new File(dirFile);
                sqlFileList = Arrays.asList(sqlDir.listFiles((dir, name) -> name.endsWith(".sql") && name.matches("\\D*\\d+\\.sql")));
            } else if ("jar".equals(resource.getProtocol())) {
                sqlFileList = getJarSqlFiles(dirFile);
            } else {
                return Collections.EMPTY_LIST;
            }
        }
        Collections.sort(sqlFileList, sqlFileComparator);
        return sortedAllFiles = sqlFileList;
    }

    private List<File> getJarSqlFiles(String dirFile) {
        List<File> sqlFileList;
        sqlFileList = new ArrayList<>();
        LOG.info("sql dir:"+dirFile);
        String jarPath = dirFile.substring(dirFile.indexOf("file:")+5,dirFile.indexOf("!"));
        JarFile jarFile = null;
        try {
            jarFile = new JarFile(jarPath);
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()){
                JarEntry jarEntry = entries.nextElement();
                if(Objects.equals(new File(jarEntry.getName()).getParent(), new File(ConfigInfo.getSqlDir()).getName())){
                    InputStream is = jarFile.getInputStream(jarEntry);
                    File file = new File(GlobalUtils.TMPDIR,jarEntry.getName());
                    FileUtils.copyInputStreamToFile(is, file);
                    file.deleteOnExit();
                    sqlFileList.add(file);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            FileUtils.closeQuietly(jarFile);
        }
        return sqlFileList;
    }

    @Override
    public List<File> getSortSqlFileAfter(String startVersion) {
        return getSortSqlFiles().stream()
                .filter(file -> {
                    //如果startVersion 为空则表示更新所有的sql文件
                    return startVersion == null || startVersion.isEmpty() || sqlVersionComparator.compare(sqlFileVersionConverter.apply(file.getName()), startVersion) > 0;
                })
                .collect(Collectors.toList());
    }

    @Override
    public String getFileVersion(File file) {
        return sqlFileVersionConverter.apply(file.getName());
    }

    @Override
    public File findSqlFileByVersion(String version) {
        List<File> sortSqlFiles = getSortSqlFiles();
        for(File file:sortSqlFiles){
            String versiontmp = sqlFileVersionConverter.apply(file.getName());
            if(Objects.equals(versiontmp,version)){
                return file;
            }
        }
        return null;
    }
}
